The Picture Viewer Handler
Let’s examine
another scenario that involves custom HTTP handlers. Thus far, we have
explored custom resources and realized how important it is to register
any custom extensions with IIS.
To speed up processing,
IIS claims the right of personally serving some resources that
typically form a Web application without going down to a particular
ISAPI extension. The list includes static files such as images and HTML
files. What if you request a GIF or a JPG file directly from the address
bar of the browser? IIS retrieves the specified resource, sets the
proper content type on the response buffer, and writes out the bytes of
the file. As a result, you’ll see the image in the browser’s page. So
far so good.
What if you point
your browser to a virtual folder that contains images? In this case, IIS
doesn’t distinguish the contents of the folder and returns a list of
files, as shown in Figure 4.
Wouldn’t it be nice if you could get a preview of the contained pictures, instead?
Designing the HTTP Handler
To start out, you need to
decide how you would let IIS know about your wishes. You can use a
particular endpoint that, appended to a folder’s name, convinces IIS to
yield to ASP.NET and provide a preview of contained images. Put another
way, the idea is binding our picture viewer handler to a particular
endpoint—say, folder.axd.A fixed endpoint for handlers
doesn’t have to be an existing, deployed resource. You make the folder.axd endpoint follow the folder name, as shown here:
http://www.contoso.com/images/folder.axd
The handler will process the URL, extract the folder name, and select all the contained pictures.
Note
In ASP.NET, the .axd extension is commonly used for endpoints referencing a special service. Trace.axd for tracing and WebResource.axd for script and resources injection are examples of two popular uses of the extension. In particular, the Trace.axd
handler implements the same logic described here. If you append its
name to the URL, it will trace all requests for pages in that
application. |
Implementing the HTTP Handler
The picture viewer
handler returns a page composed of a multirow table showing as many
images as there are in the folder. Here’s the skeleton of the class:
class PictureViewerInfo
{
public PictureViewerInfo() {
DisplayWidth = 200;
ColumnCount = 3;
}
public int DisplayWidth;
public int ColumnCount;
public string FolderName;
}
public class PictureViewerHandler : IHttpHandler
{
// Override the ProcessRequest method
public void ProcessRequest(HttpContext context)
{
PictureViewerInfo info = GetFolderInfo(context);
string html = CreateOutput(info);
// Output the data
context.Response.Write("<html><head><title>");
context.Response.Write("Picture Web Viewer");
context.Response.Write("</title></head><body>");
context.Response.Write(html);
context.Response.Write("</body></html>");
}
// Override the IsReusable property
public bool IsReusable
{
get { return true; }
}
...
}
Retrieving the actual path of the folder is as easy as stripping off the folder.axd
string from the URL and trimming any trailing slashes or backslashes.
Next, the URL of the folder is mapped to a server path and processed
using the .NET Framework API for files and folders:
private ArrayList GetAllImages(string path)
{
string[] fileTypes = { "*.bmp", "*.gif", "*.jpg", "*.png" };
ArrayList images = new ArrayList();
DirectoryInfo di = new DirectoryInfo(path);
foreach (string ext in fileTypes)
{
FileInfo[] files = di.GetFiles(ext);
if (files.Length > 0)
images.AddRange(files);
}
return images;
}
The DirectoryInfo class provides some helper functions on the specified directory; for example, the GetFiles method selects all the files that match the given pattern. Each file is wrapped by a FileInfo object. The method GetFiles
doesn’t support multiple search patterns; to search for various file
types, you need to iterate for each type and accumulate results in an
array list or equivalent data structure.
After you get all the
images in the folder, you move on to building the output for the
request. The output is a table with a fixed number of cells and a
variable number of rows to accommodate all selected images. The image is
not downloaded as a thumbnail, but it is more simply rendered in a
smaller area. For each image file, a new <img> tag is created through the Image control. The width
attribute of this file is set to a fixed value (say, 200 pixels),
causing most modern browsers to automatically resize the image.
Furthermore, the image is wrapped by an anchor that links to the same
image URL. As a result, when the user clicks on an image, the page
refreshes and shows the same image at its natural size.
string CreateOutputForFolder(PictureViewerInfo info)
{
ArrayList images = GetAllImages(info.FolderName);
Table t = new Table();
int index = 0;
bool moreImages = true;
while (moreImages)
{
TableRow row = new TableRow();
t.Rows.Add(row);
for (int i = 0; i < info.ColumnCount; i++)
{
TableCell cell = new TableCell();
row.Cells.Add(cell);
// Create the image
Image img = new Image();
FileInfo fi = (FileInfo)images[index];
img.ImageUrl = fi.Name;
img.Width = Unit.Pixel(info.DisplayWidth);
// Wrap the image in an anchor so that a larger image
// is shown when the user clicks
HtmlAnchor a = new HtmlAnchor();
a.HRef = fi.Name;
a.Controls.Add(img);
cell.Controls.Add(a);
// Check whether there are more images to show
index++;
moreImages = (index < images.Count);
if (!moreImages)
break;
}
}
}
You might want to make
the handler accept some optional query string parameters, such as width
and column count. These values are packed in an instance of the helper
class PictureViewerInfo
along with the name of the folder to view. Here’s the code to process
the query string of the URL to extract parameters if any are present:
PictureViewerInfo info = new PictureViewerInfo();
object p1 = context.Request.Params["Width"];
object p2 = context.Request.Params["Cols"];
if (p1 != null)
Int32.TryParse((string)p1, out info.DisplayWidth);
if (p2 != null)
Int32.TryParse((string)p2, out info.ColumnCount);
Figure 5 shows the handler in action.
Registering the handler is easy too. You just add the following script to the web.config file:
<add verb="*" path="folder.axd"
type="Core35.Components.PictureViewerHandler,Core35Lib" />
You place the assembly in the GAC and move the configuration script to the global web.config to extend the settings to all applications on the machine.